package com.shaoyu.spring.study.validate.core;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Multimap;
import com.shaoyu.spring.study.validate.annotation.ValidateFor;
import com.shaoyu.spring.study.validate.annotation.ValidateInterceptMethod;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Desc :
 * @Author: shaoyu
 * @Date : 2022/8/23 09:19
 */
public class ValidateInterceptorExecutor {

    public static final String DEFAULT = "default";
    private static final char SPLIT_CHAR = ':';

    /**
     * 拦截器缓存
     */
    private static final Multimap<String, ValidateInterceptor> IDENTIFIER_INTERCEPTORS = ArrayListMultimap.create();

    /**
     * 被拦截方法参数类型缓存
     */
    private static final Map<String, Map<String, Class>> METHOD_PARAMETER_TYPS = new HashMap<>();


    public static void addInterceptor(Object bean, Method method, ValidateFor validateFor) {
        String identifier = validateFor.value();
        if (DEFAULT.equals(identifier)) {
            identifier = bean.getClass().getName() + "#" + method.getName();
        }
        IDENTIFIER_INTERCEPTORS.put(identifier, new ValidateInterceptor(bean, method));
    }

    public static Object intercept(ProceedingJoinPoint proceedingJoinPoint) throws InvocationTargetException, IllegalAccessException {
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Object[] args = proceedingJoinPoint.getArgs();
        Method methodWithInterceptor = signature.getMethod();
        return invokeInterceptorMethod(methodWithInterceptor, signature, args);
    }

    private static Object invokeInterceptorMethod(Method methodWithInterceptor, MethodSignature signature, Object[] args) throws InvocationTargetException, IllegalAccessException {
        List<String> identifiers = getIdentifiers(methodWithInterceptor);
        BiMap<String, String> parameterAliasName = getParameterAliasName(methodWithInterceptor);
        if (CollectionUtils.isEmpty(identifiers)) {
            return null;
        }
        for (String identifier : identifiers) {
            Collection<ValidateInterceptor> validateInterceptors = IDENTIFIER_INTERCEPTORS.get(identifier);
            if (CollectionUtils.isEmpty(validateInterceptors)) {
                continue;
            }
            for (ValidateInterceptor validateInterceptor : validateInterceptors) {
                Method interceptorMethod = validateInterceptor.getMethod();
                if (AnnotationUtils.findAnnotation(interceptorMethod, ValidateInterceptMethod.class) != null) {
                    Object result = invokeInterceptorMethod(interceptorMethod, signature, args);
                    if (Objects.nonNull(result)) {
                        return result;
                    }
                }
                checkParameters(signature, parameterAliasName, validateInterceptor);
                Object[] interceptorArgs = buildInterceptorArgs(signature, parameterAliasName, args, validateInterceptor);
                Object invoke = validateInterceptor.invoke(interceptorArgs);
                if (ValidateContext.skip()) {
                    return null;
                }
                if (Objects.nonNull(invoke)) {
                    return invoke;
                }
            }
        }
        return null;
    }

    /**
     * @param methodWithInterceptor
     * @return k=拦截器参数名,v=原方法参数名
     */
    private static BiMap<String, String> getParameterAliasName(Method methodWithInterceptor) {
        ValidateInterceptMethod annotation = AnnotationUtils.findAnnotation(methodWithInterceptor, ValidateInterceptMethod.class);
        String[] alias = annotation.alias();
        BiMap<String, String> aliasNameMap = HashBiMap.create();
        if (alias == null || alias.length == 0) {
            return aliasNameMap;
        }
        for (String s : alias) {
            int index;
            if ((index = s.indexOf(SPLIT_CHAR)) > 0) {
                aliasNameMap.put(s.substring(index + 1), s.substring(0, index));
            }
        }
        return aliasNameMap;
    }

    private static void checkParameters(MethodSignature signature, BiMap<String, String> parameterAliasName, ValidateInterceptor validateInterceptor) {
        Map<String, Class> parameterTypeMap = buildMethodParameterTypeMap(signature);
        Map<String, Class> argTypesMap = validateInterceptor.getArgTypesMap();
        for (Map.Entry<String, Class> argTypeEntry : argTypesMap.entrySet()) {
            String argName = argTypeEntry.getKey();
            Class parameterType = parameterTypeMap.get(argName);
            boolean useAliasName = false;
            String aliasName = "";
            if (parameterType == null) {
                aliasName = parameterAliasName.get(argName);
                if (aliasName != null) {
                    parameterType = parameterTypeMap.get(aliasName);
                    useAliasName = true;
                }
                if (parameterType == null) {
                    String msg = String.format("拦截器类[%s]的拦截方法[%s]含有参数[%s],在被拦截的方法[%s]中找不到该参数,请检查或为其他参数设置别名!",
                            validateInterceptor.getClassName(),
                            validateInterceptor.getMethodName(),
                            argName,
                            signature.getName()
                    );
                    throw new IllegalArgumentException(msg);
                }
            }
            Class argType = argTypeEntry.getValue();
            if (!parameterType.getName().equals(argType.getName())) {
//                String originParamName = useAliasName? argName+"(原参数名["+aliasName+"])" : argName;
                String msg = String.format("拦截器类[%s]的拦截方法[%s]参数[%s]类型为[%s],与被拦截的方法[%s]参数[%s]类型[%s]不匹配,请检查!",
                        validateInterceptor.getClassName(),
                        validateInterceptor.getMethodName(),
                        argName, argType.getSimpleName(),
                        signature.getName(), useAliasName ? aliasName : argName, parameterType.getSimpleName()
                );
                throw new IllegalArgumentException(msg);
            }
        }
    }

    private static Map<String, Class> buildMethodParameterTypeMap(MethodSignature signature) {
        String[] parameterNames = signature.getParameterNames();
        Class[] parameterTypes = signature.getParameterTypes();
        if (parameterNames == null || parameterNames.length <= 0) {
            return Collections.EMPTY_MAP;
        }
        String signatureName = signature.getName();
        Map<String, Class> parameterTypeMap = METHOD_PARAMETER_TYPS.getOrDefault(signatureName, new HashMap<>());
        if (MapUtils.isEmpty(parameterTypeMap)) {
            for (int i = 0; i < parameterNames.length; i++) {
                parameterTypeMap.put(parameterNames[i], parameterTypes[i]);
            }
            METHOD_PARAMETER_TYPS.put(signatureName, parameterTypeMap);
        }
        return parameterTypeMap;
    }

    private static Object[] buildInterceptorArgs(MethodSignature signature, BiMap<String, String> parameterAliasName, Object[] args, ValidateInterceptor validateInterceptor) {
        Map<String, Object> argsMap = buildOriginArgsMap(signature, args);
        List<String> argNames = validateInterceptor.getArgNames();
        Object[] interceptorArgs = new Object[argNames.size()];
        for (int i = 0; i < interceptorArgs.length; i++) {
            Object o = argsMap.get(argNames.get(i));
            if (o == null) {
                o = argsMap.get(parameterAliasName.get(argNames.get(i)));
            }
            interceptorArgs[i] = o;
        }
        return interceptorArgs;
    }

    private static Map<String, Object> buildOriginArgsMap(MethodSignature signature, Object[] args) {
        String[] parameterNames = signature.getParameterNames();
        Map<String, Object> argsMap = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            argsMap.put(parameterNames[i], args[i]);
        }
        return argsMap;
    }

    private static List<String> getIdentifiers(Method methodWithInterceptor) {
        ValidateInterceptMethod annotation = AnnotationUtils.findAnnotation(methodWithInterceptor, ValidateInterceptMethod.class);
        if (Objects.isNull(annotation)) {
            return new ArrayList<>(0);
        }
        String[] value = annotation.value();
        String defaultIdentifier = methodWithInterceptor.getDeclaringClass().getName() + "#" + methodWithInterceptor.getName();
        if (value.length == 0) {
            return Collections.singletonList(defaultIdentifier);
        }
        return Stream.of(value).distinct()
                .map(identifier -> DEFAULT.equals(identifier) ? defaultIdentifier : identifier)
                .collect(Collectors.toList());
    }
}
